package ldh.im.rpc.buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class RpcByteBuffer implements RpcBuffer{

    private int size;
    private int readIdx = 0;
    private int writeIdx = 0;

    private int markReadIdx = -1;
    private int markWriteIdx = -1;

    private byte[] bytes;

    public RpcByteBuffer(int size) {
        this.size = size;
        this.bytes = new byte[size];
    }

    public byte[] array() {
        return this.bytes;
    }

    public int getReadIdx() {
        return readIdx;
    }

    public int getWriteIdx() {
        return writeIdx;
    }

    public void writeToStream(OutputStream outputStream) throws IOException {
        int readable = readable();
        outputStream.write(bytes, getReadIdx(), readable);
        this.readIdx += readable;
        this.discard();;
    }

    public int readFromStream(InputStream inputStream, int length) throws IOException {
        int writeable = writeable();
        if (length > writeable) {
            throw new RuntimeException("读空间不够");
        }
        int lg = inputStream.read(this.bytes, getWriteIdx(), length);
        if (lg > 0) {
            this.writeIdx += lg;
        }
        return lg;
    }

    public int readable() {
        if (this.writeIdx < this.readIdx) {
            throw new RuntimeException(String.format("writeIdx(%s)小于readIdx(%s)", this.writeIdx, this.readIdx));
        }
        return this.writeIdx - this.readIdx;
    }

    public int writeable() {
        if (this.writeIdx > this.size) {
            throw new RuntimeException(String.format("writeIdx(%s)大于(%s)", this.writeIdx, this.size));
        }
        return this.size - this.writeIdx;
    }

    public void writeBytes(byte[] bytes) {
        ensureSize(bytes.length);
        System.arraycopy(bytes, 0, this.bytes, this.writeIdx, bytes.length);
        this.writeIdx = this.writeIdx + bytes.length;
    }

    public void writeBytes(byte[] bytes, int start, int length) {
        ensureSize(length);
        System.arraycopy(bytes, start, this.bytes, this.writeIdx, length);
        this.writeIdx = this.writeIdx + length;
    }

    public void writeShort(short value) {
        ensureSize(2);
        this.bytes[this.readIdx] = (byte) (value>>8);
        this.bytes[this.readIdx+1] = (byte) value;
        this.writeIdx += 2;
    }

    public void writeInt(int value) {
        ensureSize(4);
        this.bytes[this.writeIdx++] = (byte) ((value >> 24) & 0xFF);
        this.bytes[this.writeIdx++] = (byte) ((value >> 16) & 0xFF);
        this.bytes[this.writeIdx++] = (byte) ((value >> 8) & 0xFF);
        this.bytes[this.writeIdx++] = (byte) (value & 0xFF);
    }

    public void writeByte(byte bb) {
        ensureSize(1);
        this.bytes[this.writeIdx] = bb;
        this.writeIdx = this.writeIdx + 1;
    }

    public void readBytes(byte[] bytes) {
        System.arraycopy(this.bytes, this.readIdx, bytes, 0, bytes.length);
        this.readIdx += bytes.length;
    }

    public short readShort() {
        if (this.readable() >= 2) {
            byte b1 = this.bytes[this.readIdx];
            byte b2 = this.bytes[this.readIdx + 1];
            short value = (short)((short)(b1 << 8) + b2);
            this.readIdx +=2;
            return value;
        } else {
            throw new RuntimeException("读空间不够");
        }
    }

    public int readInt() {
        int value = 0;
        value += this.bytes[this.readIdx++]<<24;
        value += this.bytes[this.readIdx++]<<16;
        value += this.bytes[this.readIdx++]<<8;
        value += this.bytes[this.readIdx++];
        return value;
    }

    public void discard() {
        if (this.readIdx > 0) {
            int capacity = readable();
            if (this.bytes.length > size) {
                byte[] newBytes = new byte[size];
                System.arraycopy(this.bytes, this.readIdx, newBytes, 0, capacity);
                this.bytes = newBytes;
            } else {
                System.arraycopy(this.bytes, this.readIdx, this.bytes, 0, capacity);
            }
            this.readIdx = 0;
            this.writeIdx = capacity;
        }
    }

    public void markReadIdx() {
        this.markReadIdx = this.readIdx;
    }

    public void resetMarkReadIdx() {
        if (this.markReadIdx > 0) {
            this.readIdx = this.markReadIdx;
        } else {
            throw new RuntimeException("markReadIdx未设置");
        }
    }

    public void markWriteIdx() {
        this.markWriteIdx = this.writeIdx;
    }

    public void resetMarkWriteIdx() {
        if (this.markWriteIdx > 0) {
            this.writeIdx = this.markWriteIdx;
        } else {
            throw new RuntimeException("markWriteIdx未设置");
        }
    }

    public void ensureSize(int size) {
        if (this.size - writeIdx < size) {
            int lg = (int) (this.size * 1.5);
            while(lg - writeIdx < size) {
                lg = (int) (lg * 1.5);
            }
            byte[] newBytes = new byte[lg];
            System.arraycopy(this.bytes, 0, newBytes, 0, this.size);
            this.size = lg;
            this.bytes = newBytes;
        }
    }

    public void skip(int length) {
        if (readable() >= length) {
            this.readIdx += length;
        }
    }
}
